package dd.lo.chapter2;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.MatOfByte;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.videoio.VideoCapture;
import org.opencv.videoio.Videoio;

import java.io.ByteArrayInputStream;
import java.util.Date;

/**
 * 对应程序示例
 * Example 2-10. The same object can load videos from a camera or a file
 */
public class Example6 extends Application {
    private VideoCapture cap;
    private ImageView imgView;
    private Label fpsLabel;
    private volatile boolean run = true;
    public static void main(String[] args) {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        launch(args);
    }

    @Override
    public void stop() throws Exception {
        if (null != cap) {
            cap.release();
        }
        super.stop();
    }

    @Override
    public void start(Stage stage) {
        run = true;
        //打开摄像头
        cap = new VideoCapture();
        cap.open(0);
        if (!cap.isOpened()) {
            System.out.println("Couldn't open capture.");
            Platform.exit();
        }
        //读取摄像头捕获图片的宽高，用于决定窗口的大小。MBP的摄像头为(1280, 720)
        int w = (int) cap.get(Videoio.CAP_PROP_FRAME_WIDTH);
        int h = (int) cap.get(Videoio.CAP_PROP_FRAME_HEIGHT);
        System.out.printf("Camera resolution (%d, %d)\n", w, h);
        //提供一个退出的按钮
        Button exitBtn = new Button("Exit");
        exitBtn.setOnMouseClicked(mouseEvent -> {
            //设置播放状态为false
            run = false;
            try {
                //需要停几十毫秒，以便从摄像头捕获图像的线程能够跑完一个循环并感知到run状态的变化
                Thread.sleep(50L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            //然后再执行整个程序的退出
            Platform.exit();
        });
        //提供一个显示FPS的文字信息
        fpsLabel = new Label("FPS:");
        //将整个窗口最下面的一行作为按钮和文字信息放置的位置
        FlowPane bottomPane = new FlowPane();
        bottomPane.getChildren().addAll(exitBtn, fpsLabel);
        imgView = new ImageView();
        //整个窗口放置一个画面和最下面一行放按钮和文字信息（30个像素高）
        BorderPane root = new BorderPane();
        root.setCenter(imgView);
        root.setBottom(bottomPane);
        Scene scene = new Scene(root, w, h + 30);
        stage.setScene(scene);
        stage.setTitle("Example6-Camera capturing");
        stage.setResizable(false);
        //窗口的元素已准备好，开启图像捕捉线程
        startCapturingCamera();
        //已就绪，展示主窗口
        stage.show();
    }

    private void startCapturingCamera() {
        Task<Void> task = new Task<Void>() {
            //一些状态信息用于计算真正的FPS数值
            private long startTime;
            private int fCnt;
            @Override
            protected Void call() {
                //这里输出的结果是1.000，说明如果读取摄像头的FPS是有问题的（读取视频的FPS倒是可以的）
                System.out.printf("Camera fps read from VideoCapture: %f\n", cap.get(Videoio.CAP_PROP_FPS));
                startTime = new Date().getTime();
                fCnt = 0;
                for (;;) {
                    if (!run) break;
                    //通过大概计算一秒内的帧数处理粗略估算FPS，MBP的摄像头实际帧率在30上下
                    long now = new Date().getTime();
                    if (now - startTime >= 1000L) {
                        //这里有个比较容易弄不好的地方，就是FPS文字信息需要先处理成String
                        //如果通过匿名函数的方式直接传入fCnt就会发现文字信息里面fCnt都是0
                        //因为JAVA的匿名函数传入的值都是形式上必须是final的，也就是fCnt永远都只用0这个值
                        String fpsLabelText = String.format("FPS: %d", fCnt);
                        Platform.runLater(() -> fpsLabel.setText(fpsLabelText));
                        fCnt = 0;
                        startTime = now;
                    }
                    //下面也是读帧、处理帧、显示帧的过程
                    Mat frame = new Mat();
                    cap.read(frame);
                    if (frame.empty()) {
                        System.out.println("No image captured, exiting...");
                        break;
                    }
                    MatOfByte buffer = new MatOfByte();
                    Imgcodecs.imencode(".jpeg", frame, buffer);
                    //显示帧必须放到Platform.runLater中执行，因为这里不是主线程
                    Platform.runLater(() -> imgView.setImage(new Image(new ByteArrayInputStream(buffer.toArray()))));
                    ++fCnt;
                }
                return null;
            }
        };
        //记得将task放到线程中并执行
        new Thread(task).start();
    }
}
